import asyncio
import json
import traceback

from bson import ObjectId

from ioservice.activity import aggregate_activities_raw
from ioservice.activity_register import aggregate_activity_registers_raw
from ioservice.alumni.alumni import update_alumni_by_id
from models.alumni.alumni import Alumni
from utils.dbutil import get_lookup_stages, LookupConfig
from utils.iterutil import generate_batch
from utils.jsonutil import JsonCustomEncoder
from loguru import logger

# TODO: 需要优化，统一封装捕捉异常


async def after_update_business(business_id):
    agg_pip = [
        {"$match": {"business_id": ObjectId(business_id)}},
        {"$project": {"_id": 1}},
        {"$lookup": {
            "from": "activity_register",
            "localField": "_id",
            "foreignField": "activity_id",
            "as": "register_record",
            "pipeline": [{"$match": {"is_attended": True}}]
        }},
        {"$unwind": "$register_record"},
        {"$replaceRoot": {"newRoot": "$register_record"}},
        {"$group": {"_id": "$person_id"}}
    ]
    # print(json.dumps(agg_pip, ensure_ascii=False, cls=JsonCustomEncoder))
    agg_result = await aggregate_activities_raw(agg_pip)
    person_ids = [str(r['_id']) for r in agg_result]
    logger.info(f"TASK INFO::aggregate_activities::business_id [{business_id}] relates people {len(person_ids)}")
    if not person_ids:
        return
    await batch_update_computing_fields(person_ids, caller="after_update_business")


async def after_delete_businesses(business_ids):
    agg_pip = [
        {"$match": {"business_id": {"$in": [ObjectId(i) for i in business_ids]}}},
        {"$project": {"_id": 1}},
        {"$lookup": {
            "from": "activity_register",
            "localField": "_id",
            "foreignField": "activity_id",
            "as": "register_record",
            "pipeline": [{"$match": {"is_attended": True}}]
        }},
        {"$unwind": "$register_record"},
        {"$replaceRoot": {"newRoot": "$register_record"}},
        {"$group": {"_id": "$person_id"}}
    ]
    agg_result = await aggregate_activities_raw(agg_pip)
    person_ids = [str(r['_id']) for r in agg_result]
    if not person_ids:
        return
    await batch_update_computing_fields(person_ids, caller="after_delete_businesses")


async def after_update_activity(activity_ids):
    logger.info(f"TASK INFO::after_update_activity::activity_ids [{len(activity_ids)}]")
    for i, activity_batch in enumerate(generate_batch(activity_ids)):
        agg_pip = [
            {"$match": {"activity_id": {"$in": [ObjectId(i) for i in activity_batch]}, "is_attended": True}},
            {"$group": {"_id": "$person_id"}}
        ]
        agg_result = await aggregate_activity_registers_raw(agg_pip)
        person_ids = [str(r['_id']) for r in agg_result]
        logger.info(f"TASK INFO::after_update_activity::activity_id batch {i} [{len(activity_ids)}] relates people {len(person_ids)}")
        if not person_ids:
            continue
        await batch_update_computing_fields(person_ids, caller=f"after_update_activity::activity_id batch {i}")


def extract_target_value(agg_result, output_field, stats_field):
    if not agg_result:
        return 0
    output_result = agg_result[0].get(output_field)
    if not output_result:
        return 0
    stats_result = output_result[0].get(stats_field)
    return stats_result


async def update_computing_fields(person_id):
    agg_pip = [{"$match": {"person_id": ObjectId(person_id), "is_attended": True}}]
    activity_lookup = LookupConfig(from_="activity", local_field="activity_id", foreign_field="_id",
                                   as_field="activity", fields=["business_id", "is_deleted", "duration"], ref_type="object")
    agg_pip.extend(get_lookup_stages([activity_lookup]))
    agg_pip.append({"$match": {"activity.is_deleted": {"$ne": True}}})  # 活动删除不参与计算
    business_lookup = LookupConfig(from_="business", local_field="activity.business_id", foreign_field="_id",
                                   as_field="activity.business", fields=["priority", "is_deleted"], ref_type="object")
    agg_pip.extend(get_lookup_stages([business_lookup]))
    agg_pip.append({"$facet": {
        "alumni_score": [
            {"$match": {
                "activity.business.is_deleted": {"$ne": True},  # 业务删除则不参与校友值计算
                "activity.business.priority": {"$gt": 0},
                "contribution": {"$gt": 0},
            }},
            {"$addFields": {"activity_score": {"$multiply": ["$activity.business.priority", "$contribution"]}}},
            {"$group": {"_id": None, "total_score": {"$sum": "$activity_score"}}}
        ],
        "cumulative_hours": [{"$group": {"_id": None, "total_hours": {"$sum": "$activity.duration"}}}],
        "activity_count": [{"$group": {"_id": None, "count": {"$sum": 1}}}],
    }})
    # print(json.dumps(agg_pip, ensure_ascii=False, cls=JsonCustomEncoder))
    try:
        agg_result = await aggregate_activity_registers_raw(agg_pip)
        update_model = Alumni(
            alumni_score=extract_target_value(agg_result, "alumni_score", "total_score"),
            cumulative_hours=extract_target_value(agg_result, "cumulative_hours", "total_hours"),
            num_of_activities=extract_target_value(agg_result, "activity_count", "count"),
        )
        await update_alumni_by_id(person_id, update_model)
    except Exception as e:
        logger.error(f"TASK ERROR::update_alumni_computing_fields::person_id [{person_id}]::error {e}::"
                     f"mongo pipline: {json.dumps(agg_pip, ensure_ascii=False, cls=JsonCustomEncoder)}::"
                     f"details: {traceback.format_exc()}")


async def batch_update_computing_fields(person_ids, caller=''):
    if caller:
        caller = caller + "::"
    logger.info(f"TASK INFO::{caller}batch_update_computing_fields::total people {len(person_ids)}")
    for i, batch in enumerate(generate_batch(person_ids)):
        logger.info(f"TASK INFO::{caller}batch_update_computing_fields::process person batch {i}, people {len(batch)}")
        await asyncio.gather(*[update_computing_fields(person_id) for person_id in batch])
